package Prism.extensions.port.distribution;

import Prism.core.*;

import java.io.*;
import java.net.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.concurrent.Semaphore;

/**
 *Connection object used by the SocketDistribution to connect.
 */ 

public class Connection     //extends Brick implements IPortNames
 {
   	/**
	 * 
	 */	
	 
        private AbstractDistribution parent;
	/**
	 * Socket belonging to this connection
	 */
        private Socket socket;

        private ObjectOutputStream writer;


	/**
        * Thread that sits at the socket for this connection and creates new events when
        * they arrive
        */
        private ReadConnectionThread readConn;
        
        public Connection (AbstractDistribution p, String hostName, int portNum) 
        	throws IOException
        {
                parent=p;
//                try
//                {
                    socket=new Socket(hostName,portNum);
//                    socket.setReceiveBufferSize(socket.getReceiveBufferSize() * 8192);
//                    socket.setSendBufferSize(socket.getSendBufferSize() * 8192);
//                    System.out.println("rec: " + socket.getReceiveBufferSize() + "send: " + socket.getSendBufferSize());
                    writer = new ObjectOutputStream(socket.getOutputStream());

                    if (Prism.core.PrismConstants.DEBUG)
                    	System.out.println("local port:"+socket.getLocalPort());
                    readConn = new ReadConnectionThread(this,  socket);
                    readConn.start();
//                }
//                catch (Exception e)
//                {
//                    System.out.println("\nException in Connection: constructor ->" + e.toString());
//                    //this.reconnect();
//                }
	}

        public Connection( AbstractDistribution p, Socket s)
	{
		parent = p;
		socket=s;
		try
		{
                    writer = new ObjectOutputStream(socket.getOutputStream());

                    readConn = new ReadConnectionThread(this,  socket);
                    readConn.start();
                }
		catch (IOException e)
                {
			System.out.println("\nException in Connection: constructor ->" + e.toString());
			//this.reconnect();
                }
	}

	public InetAddress getHost()
	{
		return this.socket.getInetAddress();
	}
	public int getPort()
	{
		return socket.getPort();
	}

    public AbstractDistribution getParentDistribution ()
    {
            return parent;
    }

    public synchronized void writeEvent(Object eventObject)
    {
    	try
    	{
    		/*
    		 * Jae: sending event is tracked 
    		 */
    		
    		sendTimer timer = new sendTimer(10000);
    		timer.start();
    		
    		writer.writeObject(eventObject);
    		writer.flush();
    		writer.reset();
    		
    		timer.expire();
    		
    		// System.out.println("Sent event: " + ((Event)eventObject).name);
    		
    		
    	}
    	catch(IOException ioe)
    	{
    		this.close();
    		throw new RuntimeException(ioe);
    		//this.reconnect();
    	}
    	catch(Exception e1)
    	{
    		
    		//System.out.println("%%%%%error in writing event");
    		this.close();
    		throw new RuntimeException(e1);
    		//this.reconnect();			
    	}
    }
    
    /**
	 * Message sending timer
	 * 
	 * @author Jae young Bang
	 */
	protected class sendTimer extends Thread {
		
		private int			delay_milliseconds;	
		private int			rep = 1;
		
		private boolean		expired = false;
		
		/**
		 * Semaphore to control the maximum number of concurrent XTEAM simulation threads.
		 */
		protected Semaphore 			mLock = new Semaphore (1, true);
		
		/**
		 * Locks the outgoing Events semaphore
		 */
		public void getLock() {
			try {
				mLock.acquire();	// get the semaphore
			} catch (InterruptedException ie) {
				// do nothing
			}
		}
		
		/**
		 * Releases the outgoing Event semaphore
		 */
		public void releaseLock() {
			mLock.release();
		}
		
		public sendTimer (int delay_milliseconds) {
			this.delay_milliseconds = delay_milliseconds;
		}
		
		public void run () {
			
			String ipAddr = null;
			try {
				ipAddr = Inet4Address.getLocalHost().getHostAddress();
			} catch (UnknownHostException e) {
				System.out.println("Unable to find the IP address of this host: " + e.toString());
			}
			
			while(expired == false) {
				waitForTimerExpiration();
				
				getLock();
				if(expired == false) {
					System.out.println("[" + ipAddr + "] Message transfer not completed for " + (delay_milliseconds / 1000) * (rep ++) + " seconds ...");
				} 
				releaseLock();
			}
		}
		
		protected void waitForTimerExpiration () {
			try {
				Thread.sleep(delay_milliseconds);
			} catch (InterruptedException e) {
				// do nothing
			}
		}
		
		public void expire() {
			getLock();
			expired = true;
			releaseLock();
		}
	}
        
 	public void readEvent(Object eventObject)
 	{
                //System.out.println("Received event: " + ((Event)eventObject).name);
                parent.readEvent(eventObject); 
	}        

    	public void close()
    	{
                try
		{
                    writer.close();
                    readConn.stopWorking();
                    parent.removeConnection(this);
		}
		catch (Exception e)
	    	{
                    System.out.println(" Connection: Caught exception in close() " + e.toString());
	    	}
    	}

	public void teardown()
    	{
    		try
		{
			writer.close();
			readConn.stopWorking();
		}
		catch (Exception e)
	    	{
	    		System.out.println("TBC Connection: Caught exception in teardown() " + e.toString());
	    	}
    	}



}



